/*******************************************************************************
 * Copyright (c) 2009 Remy Chi Jian Suen and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Remy Chi Jian Suen <remy.suen@gmail.com> - initial API and implementation
 ******************************************************************************/
package org.eclipse.ecf.internal.sync.ui.resources;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.datashare.IChannelContainerAdapter;
import org.eclipse.ecf.docshare2.DocShare;
import org.eclipse.ecf.internal.sync.resources.core.ResourcesShare;
import org.eclipse.ecf.internal.sync.resources.core.SyncResourcesCore;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPageListener;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.texteditor.ITextEditor;
import org.osgi.framework.BundleContext;

public class SyncResourcesUI extends AbstractUIPlugin {

	public static final String PLUGIN_ID = "org.eclipse.ecf.sync.ui.resources"; //$NON-NLS-1$

	private static SyncResourcesUI instance;

	private IWindowListener windowListener = new IWindowListener() {

		public void windowOpened(IWorkbenchWindow window) {
			window.addPageListener(pageListener);
		}

		public void windowDeactivated(IWorkbenchWindow window) {
			// nothing to do
		}

		public void windowClosed(IWorkbenchWindow window) {
			window.removePageListener(pageListener);
		}

		public void windowActivated(IWorkbenchWindow window) {
			// nothing to do
		}
	};

	private IPageListener pageListener = new IPageListener() {

		public void pageOpened(IWorkbenchPage page) {
			page.addPartListener(partListener);
		}

		public void pageClosed(IWorkbenchPage page) {
			page.removePartListener(partListener);
		}

		public void pageActivated(IWorkbenchPage page) {
			// nothing to do
		}
	};

	private IPartListener partListener = new IPartListener() {

		public void partOpened(IWorkbenchPart part) {
			ITextEditor editor = (ITextEditor) part
					.getAdapter(ITextEditor.class);
			if (editor != null) {
				IFile file = (IFile) editor.getEditorInput().getAdapter(
						IFile.class);
				if (file != null) {
					startSharing(editor, file);
				}
			}
		}

		public void partDeactivated(IWorkbenchPart part) {
			// nothing to do
		}

		public void partClosed(IWorkbenchPart part) {
			ITextEditor editor = (ITextEditor) part
					.getAdapter(ITextEditor.class);
			if (editor != null) {
				IFile file = (IFile) editor.getEditorInput().getAdapter(
						IFile.class);
				if (file != null) {
					stopSharing(editor, file);
				}
			}
		}

		public void partBroughtToTop(IWorkbenchPart part) {
			// nothing to do
		}

		public void partActivated(IWorkbenchPart part) {
			// nothing to do
		}
	};

	private Map sharedFiles = new HashMap();

	private Map sharedEditors = new HashMap();

	private boolean share(IFile file) {
		IPath path = file.getFullPath();
		Integer integer = (Integer) sharedFiles.get(path);
		if (integer == null) {
			sharedFiles.put(path, Integer.valueOf(1));
			return true;
		}
		sharedFiles.put(path, Integer.valueOf(integer.intValue() + 1));
		return false;
	}

	private boolean unshare(IFile file) {
		IPath path = file.getFullPath();
		Integer integer = (Integer) sharedFiles.remove(path);
		if (integer == null) {
			// if this file isn't being shared, will be null, return false
			return false;
		}

		if (integer.intValue() == 1) {
			return true;
		}

		sharedFiles.put(path, Integer.valueOf(integer.intValue() - 1));
		return false;
	}

	private void startSharing(ITextEditor editor, IFile file) {
		String projectName = file.getProject().getName();
		for (Iterator it = SyncResourcesCore.getResourceShares().iterator(); it
				.hasNext();) {
			ResourcesShare share = (ResourcesShare) it.next();
			if (share.isSharing(projectName) && share(file)) {
				DocShare docShare = getDocShare(share.getContainerID());
				try {
					IAnnotationModel annotationModel = editor
							.getDocumentProvider().getAnnotationModel(
									editor.getEditorInput());
					docShare.startSharing(share.getLocalID(), share
							.getReceiverID(), file.getFullPath().toString(),
							annotationModel);
					ISelectionProvider provider = editor.getSelectionProvider();
					if (provider instanceof IPostSelectionProvider) {
						ISelectionChangedListener listener = new SelectionChangedListener(
								share.getReceiverID(), file.getFullPath()
										.toString(), docShare);
						((IPostSelectionProvider) provider)
								.addPostSelectionChangedListener(listener);
						sharedEditors.put(editor, listener);
					}
				} catch (ECFException e) {
					IStatus status = new Status(IStatus.ERROR, PLUGIN_ID,
							"Could not send initiation request to " //$NON-NLS-1$
									+ share.getReceiverID(), e);
					log(status);
					StatusManager.getManager().handle(status,
							StatusManager.SHOW);
				} catch (CoreException e) {
					IStatus status = new Status(IStatus.ERROR, PLUGIN_ID,
							"Could not connect to the file buffer of " //$NON-NLS-1$
									+ file.getFullPath(), e);
					log(status);
					StatusManager.getManager().handle(status,
							StatusManager.SHOW);
				}
			}
		}
	}

	private void stopSharing(ITextEditor editor, IFile file) {
		String projectName = file.getProject().getName();
		for (Iterator it = SyncResourcesCore.getResourceShares().iterator(); it
				.hasNext();) {
			ResourcesShare share = (ResourcesShare) it.next();
			if (share.isSharing(projectName) && unshare(file)) {
				DocShare docShare = getDocShare(share.getContainerID());
				stopSharing(docShare, share.getReceiverID(), editor, file);
			}
		}
	}

	private void stopSharing(DocShare share, ID id, ITextEditor editor,
			IFile file) {
		try {
			share.stopSharing(id, file.getFullPath().toString());
		} catch (ECFException e) {
			log(new Status(IStatus.ERROR, PLUGIN_ID,
					"Could not send stop message to " + id, e)); //$NON-NLS-1$
		}

		ISelectionChangedListener listener = (ISelectionChangedListener) sharedEditors
				.remove(editor);
		if (listener != null) {
			ISelectionProvider provider = editor.getSelectionProvider();
			if (provider instanceof IPostSelectionProvider) {
				((IPostSelectionProvider) provider)
						.removePostSelectionChangedListener(listener);
			}
		}
	}

	public SyncResourcesUI() {
		instance = this;
	}

	public void start(BundleContext context) throws Exception {
		super.start(context);

		getWorkbench().addWindowListener(windowListener);

		IWorkbenchWindow[] workbenchWindows = getWorkbench()
				.getWorkbenchWindows();
		for (int i = 0; i < workbenchWindows.length; i++) {
			workbenchWindows[i].addPageListener(pageListener);
			IWorkbenchPage[] pages = workbenchWindows[i].getPages();
			for (int j = 0; j < pages.length; j++) {
				pages[j].addPartListener(partListener);
			}
		}
	}
	
	public static SyncResourcesUI getDefault() {
		return instance;
	}
	
	public Display getDisplay() {
		return getWorkbench().getDisplay();
	}

	public static void log(IStatus status) {
		instance.getLog().log(status);
	}

	public void stop(BundleContext context) throws Exception {
		getWorkbench().removeWindowListener(windowListener);

		IWorkbenchWindow[] workbenchWindows = getWorkbench()
				.getWorkbenchWindows();
		for (int i = 0; i < workbenchWindows.length; i++) {
			workbenchWindows[i].removePageListener(pageListener);
			IWorkbenchPage[] pages = workbenchWindows[i].getPages();
			for (int j = 0; j < pages.length; j++) {
				pages[j].removePartListener(partListener);
			}
		}

		super.stop(context);
	}

	private static Map docshareChannels = new HashMap();

	public static DocShare getDocShare(ID containerID) {
		return (DocShare) docshareChannels.get(containerID);
	}

	public static DocShare addDocShare(ID containerID,
			IChannelContainerAdapter channelAdapter) throws ECFException {
		DocShare docShare = (DocShare) docshareChannels.get(containerID);
		if (docShare == null) {
			return (DocShare) docshareChannels.put(containerID,
					new WorkbenchAwareDocShare(channelAdapter));
		}
		return docShare;
	}

	public static DocShare removeDocShare(ID containerID) {
		return (DocShare) docshareChannels.remove(containerID);
	}

	public static ResourcesShare getResourceShare(ID containerID) {
		return SyncResourcesCore.getResourcesShare(containerID);
	}

	public static ResourcesShare addResourceShare(ID containerID,
			IChannelContainerAdapter channelAdapter) throws ECFException {
		ResourcesShare resourcesShare = SyncResourcesCore
				.getResourcesShare(containerID);
		if (resourcesShare == null) {
			resourcesShare = new WorkbenchAwareResourcesShare(containerID,
					channelAdapter);
			SyncResourcesCore.addResourcesShare(containerID, resourcesShare);
		}
		return resourcesShare;
	}

	public static ResourcesShare removeResourceShare(ID containerID) {
		return SyncResourcesCore.removeResourcesShare(containerID);
	}

	private class SelectionChangedListener implements ISelectionChangedListener {

		private ID targetID;
		private String path;
		private DocShare share;

		public SelectionChangedListener(ID targetID, String path, DocShare share) {
			this.targetID = targetID;
			this.path = path;
			this.share = share;
		}

		public void selectionChanged(SelectionChangedEvent e) {
			ISelection selection = e.getSelection();
			if (selection instanceof ITextSelection) {
				ITextSelection textSelection = (ITextSelection) selection;
				try {
					share.sendSelection(targetID, path, textSelection
							.getOffset(), textSelection.getLength());
				} catch (ECFException exception) {
					log(new Status(IStatus.ERROR, PLUGIN_ID,
							"Could not send selection message to " + targetID, //$NON-NLS-1$
							exception));
				}
			}
		}

	}

}
